#include "init.h"
#include "common.h"
#include "worker_thread.h"
#include <linux/slab.h>
#include <linux/kthread.h>
#include <linux/sched/task.h>
#include <linux/err.h>

// thread config block, owned by thread
struct thread_config {
	// init block
	struct init_block block;

	// thread main function
	thread_fn_t thread_fn;

	// thread task struct
	struct task_struct * thread_task;

	// thread main function argument
	void *data;
};

static int worker_init(struct init_block *block) 
{
	int ret = 0;
	struct thread_config *cfg = container_of(block, struct thread_config, block);
	struct task_struct *task = kthread_create(cfg->thread_fn, cfg->data, block->name);

	if (IS_ERR(task)) {
		PR_ERR("worker thread [%s], kthread_create failed, errno is %ld", 
				block->name, PTR_ERR(task));
		return PTR_ERR(task);
	} else if (task == NULL) {
		PR_ERR("worker thread [%s], kthread_create failed, task is NULL", block->name);
		return -1;
	}

	cfg->thread_task = task;

	// increase task struct ref count, keep
	get_task_struct(task);

	PR_INFO("begin wake up worker thread [%s]", block->name);
	ret = wake_up_process(task);
	if (ret == 1 || ret == 0) {
		return 0;
	} else {
		return ret;
	}
}

static void worker_cleanup(struct init_block *block)
{
	int thread_exit = 0;
	struct thread_config *cfg = container_of(block, struct thread_config, block);
	struct task_struct *task = cfg->thread_task;

	PR_INFO("worker thread %s, set worker thread stop flag", block->name);
	if (task != NULL) {
		thread_exit = kthread_stop(task);
		PR_INFO("worker thread %s, stopped with status %d", block->name, thread_exit);
		put_task_struct(task);
	}
	return;
}

static void worker_drop(struct init_block *block)
{
	struct thread_config *cfg = container_of(block, struct thread_config, block);
	kfree(cfg);
	return;
}

int new_worker_thread(thread_fn_t thread_fn, const char *name, void *data) {
	struct thread_config *thread_cfg = NULL;
	struct init_block *init_block = NULL;

	thread_cfg = kmalloc(sizeof(struct thread_config), GFP_KERNEL);
	if (thread_cfg == NULL) {
		PR_INFO("kmalloc thread_config failed");
		cleanup();
		return -1;
	}
	init_block = &thread_cfg->block;
	
	thread_cfg->data = data;
	thread_cfg->thread_fn = thread_fn;
	thread_cfg->thread_task = NULL;


	strscpy(init_block->name, name, BLOCK_NAME_LEN);
	init_block->init_fn = worker_init;
	init_block->drop_fn = worker_drop;
	init_block->cleanup_fn = worker_cleanup;
	return try_init(init_block);
}

